/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.rest;

import static org.dspace.builder.ItemBuilder.createItem;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.dspace.app.rest.test.AbstractControllerIntegrationTest;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.content.Collection;
import org.dspace.content.Community;
import org.dspace.content.Item;
import org.dspace.services.ConfigurationService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MvcResult;

/**
 * Integration test to test the /sitemaps/{name} endpoint, see {@link SitemapRestController}
 *
 * @author Maria Verdonck (Atmire) on 08/07/2020
 */
public class SitemapRestControllerIT extends AbstractControllerIntegrationTest {

    @Autowired
    ConfigurationService configurationService;

    @Autowired
    ResourcePolicyService policyService;

    private final static String SITEMAPS_ENDPOINT = "sitemaps";

    private Item item1;
    private Item item2;
    private Item itemRestricted;
    private Item itemUndiscoverable;
    private Item entityPublication;
    private Item entityPublicationRestricted;
    private Item entityPublicationUndiscoverable;
    private Community community;
    private Community communityRestricted;
    private Collection collection;
    private Collection collectionRestricted;

    @Before
    @Override
    public void setUp() throws Exception {
        super.setUp();

        configurationService.setProperty("sitemap.path", SITEMAPS_ENDPOINT);

        context.turnOffAuthorisationSystem();

        community = CommunityBuilder.createCommunity(context).build();
        communityRestricted = CommunityBuilder.createCommunity(context).build();
        policyService.removeAllPolicies(context, communityRestricted);
        collection = CollectionBuilder.createCollection(context, community).build();
        collectionRestricted = CollectionBuilder.createCollection(context, community).build();
        Collection publicationCollection = CollectionBuilder.createCollection(context, community)
                .withEntityType("Publication")
                .withName("Publication Collection").build();
        policyService.removeAllPolicies(context, collectionRestricted);

        this.item1 = createItem(context, collection)
            .withTitle("Test 1")
            .withIssueDate("2010-10-17")
            .build();
        this.item2 = createItem(context, collection)
            .withTitle("Test 2")
            .withIssueDate("2015-8-3")
            .build();
        this.itemRestricted = createItem(context, collection)
                .withTitle("Test 3")
                .withIssueDate("2015-8-3")
                .build();
        policyService.removeAllPolicies(context, itemRestricted);
        this.itemUndiscoverable = createItem(context, collection)
                .withTitle("Test 4")
                .withIssueDate("2015-8-3")
                .makeUnDiscoverable()
                .build();
        this.entityPublication = createItem(context, publicationCollection)
                .withTitle("Item Publication")
                .withIssueDate("2015-8-3")
                .build();
        this.entityPublicationRestricted = createItem(context, publicationCollection)
                .withTitle("Item Publication Restricted")
                .withIssueDate("2015-8-3")
                .build();
        policyService.removeAllPolicies(context, entityPublicationRestricted);
        this.entityPublicationUndiscoverable = createItem(context, publicationCollection)
                .withTitle("Item Publication")
                .withIssueDate("2015-8-3")
                .makeUnDiscoverable()
                .build();

        runDSpaceScript("generate-sitemaps");

        context.restoreAuthSystemState();
    }

    @After
    public void destroy() throws Exception {
        // delete sitemaps generated by tests in before
        runDSpaceScript("generate-sitemaps", "-d");

        super.destroy();
    }

    @Test
    public void testSitemap_notValidSiteMapFile() throws Exception {
        //** WHEN **
        //We attempt to retrieve a non valid sitemap file
        getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/no-such-file"))
                   //** THEN **
                   .andExpect(status().isNotFound());
    }

    @Test
    public void testSitemap_fileSystemTraversal_dspaceCfg() throws Exception {
        //** WHEN **
        //We attempt to use endpoint for malicious file system traversal
        getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e/config/dspace.cfg"))
                   .andExpect(status().isBadRequest());
    }

    @Test
    public void testSitemap_fileSystemTraversal_dspaceCfg2() throws Exception {
        //** WHEN **
        //We attempt to use endpoint for malicious file system traversal
        getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/%2e%2e%2fconfig%2fdspace.cfg"))
                   .andExpect(status().isBadRequest());
    }

    @Test
    public void testSitemap_sitemapIndexHtml() throws Exception {
        //** WHEN **
        //We retrieve sitemap_index.html
        MvcResult result = getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/sitemap_index.html"))
                                      //** THEN **
                                      .andExpect(status().isOk())
                                      //We expect the content type to match
                                      .andExpect(content().contentType("text/html;charset=UTF-8"))
                                      .andReturn();

        String response = result.getResponse().getContentAsString();
        // contains a link to /sitemaps/sitemap0.html
        assertTrue(response.contains("/sitemap0.html"));
    }

    @Test
    public void testSitemap_sitemap0Html() throws Exception {
        //** WHEN **
        //We retrieve sitemap0.html
        MvcResult result = getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/sitemap0.html"))
                                      //** THEN **
                                      .andExpect(status().isOk())
                                      //We expect the content type to match
                                      .andExpect(content().contentType("text/html;charset=UTF-8"))
                                      .andReturn();

        String response = result.getResponse().getContentAsString();
        // contains a link to communities: [dspace.ui.url]/communities/<uuid>
        assertTrue(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/communities/" + community.getID()));
        // contains a link to collections: [dspace.ui.url]/collections/<uuid>
        assertTrue(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/collections/" + collection.getID()));
        // contains a link to items: [dspace.ui.url]/items/<uuid>
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID()));
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID()));
        // contains proper link to entities items
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublication.getID()));
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublication.getID()));
        // does not contain links to restricted content
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/communities/" + communityRestricted.getID()));
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/collections/" + collectionRestricted.getID()));
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemRestricted.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublicationRestricted.getID()));
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublicationRestricted.getID()));
        // does not contain links to undiscoverable content
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemUndiscoverable.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublicationUndiscoverable.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/"
                + entityPublicationUndiscoverable.getID()));

    }

    @Test
    public void testSitemap_sitemapIndexXml() throws Exception {
        //** WHEN **
        //We retrieve sitemap_index.xml
        MvcResult result = getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/sitemap_index.xml"))
                                      //** THEN **
                                      .andExpect(status().isOk())
                                      //We expect the content type to match
                                      .andExpect(res -> {
                                          String actual = res.getResponse().getContentType();
                                          assertTrue("Content Type",
                                                  "text/xml;charset=UTF-8".equals(actual) ||
                                                          "application/xml;charset=UTF-8".equals(actual));
                                      })
                                      .andReturn();

        String response = result.getResponse().getContentAsString();
        // contains a link to /sitemaps/sitemap0.html
        assertTrue(response.contains("/sitemap0.xml"));
    }

    @Test
    public void testSitemap_sitemap0Xml() throws Exception {
        //** WHEN **
        //We retrieve sitemap0.html
        MvcResult result = getClient().perform(get("/" + SITEMAPS_ENDPOINT + "/sitemap0.xml"))
                                      //** THEN **
                                      .andExpect(status().isOk())
                                      //We expect the content type to match
                                      .andExpect(res -> {
                                          String actual = res.getResponse().getContentType();
                                          assertTrue("Content Type",
                                                  "text/xml;charset=UTF-8".equals(actual) ||
                                                          "application/xml;charset=UTF-8".equals(actual));
                                      })
                                      .andReturn();

        String response = result.getResponse().getContentAsString();
        // contains a link to communities: [dspace.ui.url]/communities/<uuid>
        assertTrue(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/communities/" + community.getID()));
        // contains a link to collections: [dspace.ui.url]/collections/<uuid>
        assertTrue(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/collections/" + collection.getID()));
        // contains a link to items: [dspace.ui.url]/items/<uuid>
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item1.getID()));
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/" + item2.getID()));
        // contains proper link to entities items
        assertTrue(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublication.getID()));
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublication.getID()));
        // does not contain links to restricted content
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/communities/" + communityRestricted.getID()));
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/collections/" + collectionRestricted.getID()));
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemRestricted.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublicationRestricted.getID()));
        assertFalse(response.contains(
                configurationService.getProperty("dspace.ui.url") + "/items/" + entityPublicationRestricted.getID()));
        // does not contain links to undiscoverable content
        assertFalse(response
                .contains(configurationService.getProperty("dspace.ui.url") + "/items/" + itemUndiscoverable.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/entities/publication/"
                + entityPublicationUndiscoverable.getID()));
        assertFalse(response.contains(configurationService.getProperty("dspace.ui.url") + "/items/"
                + entityPublicationUndiscoverable.getID()));
    }
}
